/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package argentracker.activeentity.registry;

import java.util.HashMap;
import java.util.Map;

import argentracker.activeentity.ActiveEntity;
import argentracker.activeentity.exceptions.UnknownActiveEntityClassException;
import argentracker.activeentity.metadata.ActiveEntityMetadata;

import com.google.appengine.api.datastore.Entity;

/**
 * briefly description.
 * 
 * extense description.
 * 
 * @author matero (matero _at_ gmail _dot_ com)
 */
public class DefaultActiveEntityRegistryStrategy implements ActiveEntityRegistryStrategy {

    private final Map<Class<?>, ActiveEntityMetadata<?>> metadataByClass = new HashMap<Class<?>, ActiveEntityMetadata<?>>();
    private final Map<String, ActiveEntityMetadata<?>> metadataByName = new HashMap<String, ActiveEntityMetadata<?>>();

    @Override
    public boolean isActiveEntityDefinedOn(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("clazz can not be null");
        }
        return ActiveEntity.class.isAssignableFrom(clazz);
    }

    @Override
    public <AE extends ActiveEntity<AE>> void registerActiveEntity(Class<AE> aeClass) {
        if (aeClass == null) {
            throw new IllegalArgumentException("aeClass can not be null");
        }
        putMetadata(aeClass, createMetadataFor(aeClass));
    }

    private <AE extends ActiveEntity<AE>> void putMetadata(Class<AE> aeClass, ActiveEntityMetadata<AE> aeMetadata) {
        this.metadataByClass.put(aeClass, aeMetadata);
        this.metadataByName.put(aeClass.getName(), aeMetadata);
    }

    private <AE extends ActiveEntity<AE>> ActiveEntityMetadata<AE> createMetadataFor(Class<AE> aeClass) {
        return ActiveEntityMetadata.forClass(aeClass);
    }

    @Override
    public <AE extends ActiveEntity<AE>> boolean hasMetadataOf(Class<AE> aeClass) {
        if (aeClass == null) {
            throw new IllegalArgumentException("aeClass can not be null");
        }
        return this.metadataByClass.containsKey(aeClass);
    }

    @Override
    public <AE extends ActiveEntity<AE>> ActiveEntityMetadata<AE> getActiveEntityMetadata(Class<AE> aeClass) {
        if (aeClass == null) {
            throw new IllegalArgumentException("aeClass can not be null");
        }

        @SuppressWarnings("unchecked")
        ActiveEntityMetadata<AE> aeMetadata = (ActiveEntityMetadata<AE>) this.metadataByClass.get(aeClass);

        if (aeMetadata == null) {
            throw new UnknownActiveEntityClassException(aeClass);
        }

        return aeMetadata;
    }

    @Override
    public ActiveEntityMetadata<?> getActiveEntityMetadata(String className) {
        if (className == null) {
            throw new IllegalArgumentException("className can not be null");
        }
        
        ActiveEntityMetadata<?> aeMetadata = this.metadataByName.get(className);

        if (aeMetadata == null) {
            throw new UnknownActiveEntityClassException(className);
        }

        return aeMetadata;
    }

    @Override
    public <AE extends ActiveEntity<AE>> ActiveEntityMetadata<AE> installActiveEntity(Class<AE> aeClass) {
        if (aeClass == null) {
            throw new IllegalArgumentException("aeClass can not be null");
        }

        @SuppressWarnings("unchecked")
        ActiveEntityMetadata<AE> aeMetadata = (ActiveEntityMetadata<AE>) this.metadataByClass.get(aeClass);

        if (aeMetadata == null) {
            aeMetadata = createMetadataFor(aeClass);
            putMetadata(aeClass, aeMetadata);
        }

        return aeMetadata;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <AE extends ActiveEntity<AE>> AE createActiveEntityFrom(Entity e, Class<AE> aeClass) {
        final String entityClassName = (String) e.getProperty("className");
        final ActiveEntityMetadata<? extends ActiveEntity> aeMetadata = getActiveEntityMetadata(entityClassName);
        
        return aeClass.cast(aeMetadata.createActiveEntityFor(e));
    }

}
